#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>

int lock_reg(int, int, int, off_t, int, off_t); //register lock
pid_t lock_test(int, int, off_t, int, off_t);   //test  lockable

//set lock
#define read_lock(fd, offset, whence, len) \
    lock_reg((fd), F_SETLK, F_RDLCK, (offset), (whence), (len))
#define readw_lock(fd, offset, whence, len) \
    lock_reg((fd), F_SETLKW, F_RDLCK, (offset), (whence), (len))
#define write_lock(fd, offset, whence, len) \
    lock_reg((fd), F_SETLK, F_WRLCK, (offset), (whence), (len))
#define writew_lock(fd, offset, whence, len) \
    lock_reg((fd), F_SETLKW, F_WRLCK, (offset), (whence), (len))
#define un_lock(fd, offset, whence, len) \
    lock_reg((fd), F_SETLK, F_UNLCK, (offset), (whence), (len))

//Test lock
#define read_lock_pid(fd, offset, whence, len) \
    lock_test((fd), F_RDLCK, (offset), (whence), (len))
#define write_lock_pid(fd, offset, whence, len) \
    lock_test((fd), F_WRLCK, (offset), (whence), (len))
#define is_read_lockable(fd, offset, whence, len) \
    (read_lock_pid == 0)
#define is_write_lockable(fd, offset, whence, len) \
    (write_lock_pid == 0)

int lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len)
{
    struct flock lock;

    lock.l_type = type;     /* F_RDLCK, F_WRLCK, F_UNLCK */
    lock.l_start = offset;  /* byte offset, relative to l_whence */
    lock.l_whence = whence; /* SEEK_SET, SEEK_CUR, SEEK_END */
    lock.l_len = len;       /* #bytes (0 means to EOF) */

    return (fcntl(fd, cmd, &lock));
}

pid_t lock_test(int fd, int type, off_t offset, int whence, off_t len)
{
    struct flock lock;

    lock.l_type = type;     /* F_RDLCK or F_WRLCK */
    lock.l_start = offset;  /* byte offset, relative to l_whence */
    lock.l_whence = whence; /* SEEK_SET, SEEK_CUR, SEEK_END */
    lock.l_len = len;       /* #bytes (0 means to EOF) */

    if (fcntl(fd, F_GETLK, &lock) < 0) {
        printf("fcntl error for %s.\n", strerror(errno));
        return (-1);
    }

    if (lock.l_type == F_UNLCK) {
        return (0); /* false, region isn't locked by another proc */
    }

    return (lock.l_pid); /* true, return pid of lock owner */
}

void lock_set(int fd, int type)
{
    pid_t read_lock_id = 0;
    pid_t write_lock_id = 0;

    while (1) {
        //set lock according to lock type
        switch (type) {
        case F_RDLCK:
            if (read_lock(fd, 0, SEEK_SET, 0) == 0) {
                printf("read lock set by %d \n", getpid());
                return;
            }
            break;
        case F_WRLCK:
            if (write_lock(fd, 0, SEEK_SET, 0) == 0) {
                printf("write lock set by %d \n", getpid());
                return;
            }
            break;
        case F_UNLCK:
            if (un_lock(fd, 0, SEEK_SET, 0) == 0) {
                printf("release lock by %d \n", getpid());
                return;
            }
            break;
        }

        //test lock owner
        if (type == F_RDLCK) {
            if ((read_lock_id = read_lock_pid(fd, 0, SEEK_SET, 0)) != 0) {
                printf("read lock already set by %d \n", read_lock_id);
            }
        }
        else if (type == F_WRLCK) {
            if ((write_lock_id = read_lock_pid(fd, 0, SEEK_SET, 0)) != 0) {
                printf("write lock already set by %d \n", write_lock_id);
            }
        }
    }
}

int main(int argc, char **argv)
{
    int fd;

    //First open file and choose right mode by lock type
    fd = open("/tmp/hello", O_RDWR | O_CREAT, 0666);
    if (fd < 0) {
        printf("open error ! \n");
        exit(1);
    }

    //lock
    printf("press ENTER to add lock:\n");
    getchar();
    lock_set(fd, F_WRLCK);

    printf("press ENTER and exit, lock release:\n");
    getchar();
    //release lock
    lock_set(fd, F_UNLCK); //useless code, lock release if close fd
    if (close(fd) < 0) {
        printf("\n close file error ! \n");
        exit(1);
    }

    return 0;
}